package org.stephen.recommend.thread;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.stephen.recommend.enums.LanguageType;
import org.stephen.recommend.util.Utils;
import org.stephen.recommend.common.RedisKeyPrefix;
import org.stephen.recommend.config.PropGetKit;
import org.stephen.recommend.entity.Article;
import org.stephen.recommend.model.HotNewsList;
import org.stephen.recommend.model.SimilarityUsers;
import org.stephen.recommend.model.UserTags;
import org.stephen.recommend.model.ViewedArticles;
import org.stephen.recommend.service.CmsArticleService;
import org.stephen.recommend.service.IPersonService;
import org.stephen.recommend.service.TagService;
import org.stephen.recommend.util.CommonUtils;
import org.stephen.recommend.util.RedisUtils;
import org.stephen.recommend.util.SerializeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * ClassName: AsyncService
 * Description:异步处理服务，主要处理异步更新热点新闻，更新用户标签，寻找相似用户，删除推荐系统内的文章等
 * Date: 2020/12/9 16:32
 *
 * @author stephen.qiu
 */
@Component
public class AsyncService {
    private Logger logger = LoggerFactory.getLogger(AsyncService.class);

    @Autowired
    RedisUtils redisUtils;

    @Autowired
    CmsArticleService cmsArticleService;

    @Autowired
    TagService tagService;

    @Autowired
    PropGetKit propGetKit;

    @Autowired
    IPersonService personService;

    /**
     * 删除文章的时候，在推荐系统里删除对应的文章
     *
     * @param articleId
     * @param languageType
     */
    @Async("taskExecutor")
    public void deleteArticleById(String articleId, LanguageType languageType) {
        Article article = cmsArticleService.getById(articleId);
        if (article.getArticleType() != 1) {
            return;
        }
        List<String> tagsName = Stream.of(article.getTag().split(",")).map(c -> c.split(":")[0]).collect(Collectors.toList());
        List<String> tagIds = tagService.getTagListByName(tagsName).stream().map(c -> c.getId()).collect(Collectors.toList());
        for (String tagId : tagIds) {
            String articleIds = redisUtils.get(RedisKeyPrefix.getTag(languageType) + tagId);
            if (articleId.equals(articleIds)) {
                redisUtils.delete(RedisKeyPrefix.getTag(languageType) + tagId);
                continue;
            }
            if (articleIds.startsWith(articleId)) {
                articleIds = articleIds.replace(articleId + ",", "");
            } else {
                articleIds = articleIds.replace("," + articleId, "");
            }
            redisUtils.set(RedisKeyPrefix.getTag(languageType) + tagId, articleIds);
        }
    }

    @Async("taskExecutor")
    public void initHotNewsList(int beforeDays, LanguageType languageType) {
        logger.info("开始更新" + languageType.getMsg() + "热点新闻");
        HotNewsList.getInstance().setLastInitTopHotNewsListTime(LocalDateTime.now());
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("article_type", 1);
        queryWrapper.eq("language", languageType.getCode());
        queryWrapper.ge("create_date", LocalDateTime.now().plusDays(0 - beforeDays));
        queryWrapper.eq("del_flag", 0);
        queryWrapper.orderByDesc("create_date");
        List<Article> list = cmsArticleService.list(queryWrapper);
        HotNewsList.getInstance().setArticleList(list, languageType);
        logger.info(languageType.getMsg() + "热点新闻初始化完成,数量:" + list.size());
    }

    @Async("taskExecutor")
    public void findSimilarityUsers(String userId, int num) {
        logger.info("----------寻找相似用户----------");
        Set<String> keys = redisUtils.keys(RedisKeyPrefix.VIEW_ARTICLE_IDS + "*");
        keys.remove(RedisKeyPrefix.VIEW_ARTICLE_IDS + userId);
        if (Utils.isNotEmpty(keys)) {
            Map<String, Double> map = new HashMap<>(CommonUtils.initHashMapSize(0));
            ViewedArticles viewedArticles = (ViewedArticles) SerializeUtils.stringSerializeObject(redisUtils.get(RedisKeyPrefix.VIEW_ARTICLE_IDS + userId));
            HashMap<String, LocalDateTime> viewedArticleTimeMap = viewedArticles.getViewedArticleTimeMap();
            for (String key : keys) {
                ViewedArticles otherViewArticles = (ViewedArticles) SerializeUtils.stringSerializeObject(redisUtils.get(key));
                List<String> otherViewArticleIds = otherViewArticles.getViewedArticlesList(propGetKit.getCFValidDay());
                otherViewArticleIds.forEach(otherViewArticleId -> {
                    if (viewedArticleTimeMap.containsKey(otherViewArticleId)) {
                        Double i = map.getOrDefault(viewedArticles, 0.0) + 1.0;
                        map.put(key.substring(RedisKeyPrefix.VIEW_ARTICLE_IDS.length(), key.length()), i);
                    }
                });
            }
            List<Map.Entry<String, Double>> userList = new ArrayList<>(map.entrySet());
            // 升序比较器
            Collections.sort(userList, CommonUtils.getComparator());
            /**
             * 取前num个最相似的用户
             */
            userList = (num > 0 && num <= userList.size()) ? userList.subList(0, num) : userList;
            List<String> similarityUserList = userList.stream().map(c -> c.getKey()).collect(Collectors.toList());

            SimilarityUsers similarityUsers;
            if (redisUtils.hasKey(RedisKeyPrefix.SIMILARITY_USERS + userId)) {
                similarityUsers = (SimilarityUsers) SerializeUtils.stringSerializeObject(redisUtils.get(RedisKeyPrefix.SIMILARITY_USERS + userId));
                similarityUsers.updateSimilarityUsers(propGetKit.getPreviousDays());
            } else {
                similarityUsers = new SimilarityUsers();
            }
            int size = 0;

            size += similarityUsers.updateSimilarityUsers(similarityUserList);

            List<String> followIds = personService.getFollowsList(userId);

            size += similarityUsers.updateSimilarityUsers(followIds);

            if (similarityUserList.size() > 0 || followIds.size() > 0) {
                redisUtils.set(RedisKeyPrefix.SIMILARITY_USERS + userId, SerializeUtils.serializeObject(similarityUsers));
            }
            logger.info("异步寻找" + size + "个相似用户成功");
        } else {
            logger.info("----------无相似用户----------");
        }
    }

    @Async("taskExecutor")
    public void updateUserTags(String articleId, String userId, LanguageType languageType) {
        UserTags userTags;
        HashMap<String, Double> tagMap = cmsArticleService.getTagMapByArticles(articleId.split(","), languageType);
        if (redisUtils.hasKey(RedisKeyPrefix.getUserTag(languageType) + userId)) {
            userTags = (UserTags) SerializeUtils.stringSerializeObject(redisUtils.get(RedisKeyPrefix.getUserTag(languageType) + userId));
            userTags.updateMap(propGetKit.getDeclineFactor(), tagMap);
            userTags.updateUserTag(propGetKit.getPreviousDays());
        } else {
            userTags = new UserTags();
            userTags.createUserTags(tagMap);
        }
        redisUtils.set(RedisKeyPrefix.getUserTag(languageType) + userId, SerializeUtils.serializeObject(userTags));
        logger.info("异步更新用户标签成功");
    }
}